package cn.yzj.openapi.utils;

import cn.yzj.common.HttpMethod;
import cn.yzj.http.YzjHttp;
import cn.yzj.http.YzjHttpResult;
import cn.yzj.http.YzjSendConfig;
import cn.yzj.openapi.enums.error.TiebaSignErrorEnums;
import cn.yzj.openapi.exception.OpenApiException;
import cn.yzj.utils.ThreadPoolConfigUtils;
import com.alibaba.fastjson.JSONObject;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 贴吧工具类
 *
 * @author gzkemays
 * @since 2022/4/28 20:48
 */
@Data
@Slf4j
public class TiebaUtils {
  public static String TBS_URL = "https://tieba.baidu.com/dc/common/tbs";
  public static String LIKE_URL = "https://tieba.baidu.com/i/i/forum";
  public static String FOLLOWS_URL = "https://tieba.baidu.com/f/like/mylike";
  public static String SIGN_URL =
      "https://tieba.baidu.com/mo/q/sign?tbs=${TBS}&kw=${KW}&is_like=1&fid=${FID}";
  public static String TIEBA_ERROR = "贴吧目录出问题啦，请到贴吧签到吧反馈";
  public static String USER_NAME_DOC_CLASS = "aside_user_name";
  public static String PAGINATION_DOC_CLASS = "pagination";
  public static String FORUM_TABLE_DOC_CLASS = "forum_table";
  public static String TD_TITLE_ATTR_NAME = "title";
  public static String RESULT_NO_KEY = "no";
  public static String MSG_KEY = "error";
  public static String SUCCESS_CODE = "0";
  public static String REMOVE_CODE = "1010";
  public static String RE_SIGN = "1101";
  public static String FIRST_PAGE = "1";
  public static final ThreadPoolExecutor POOL_EXECUTOR =
      ThreadPoolConfigUtils.builder()
          .createThreadPool()
          .corePoolSize(4)
          .maximumPoolSize(8)
          .openAllowCoreThread(10, TimeUnit.MINUTES)
          .build()
          .getThreadPoolExecutor();

  Document document;
  String cookie;
  String username;
  List<String> follows = new ArrayList<>();

  @Data
  @AllArgsConstructor
  @NoArgsConstructor
  public static class FollowStatus {
    String follow;
    String status;
    String msg;
  }

  public TiebaUtils(String cookie) {
    this.cookie = cookie;
    YzjHttpResult send =
        YzjHttp.createGet(LIKE_URL)
            .cookies(cookie)
            .config(YzjSendConfig.start().document().end())
            .send();
    document = send.getDocument();
    // 获取用户名
    Elements username = document.getElementsByClass(USER_NAME_DOC_CLASS);
    if (CollectionUtils.isEmpty(username)) {
      throw OpenApiException.tiebaSignException(TiebaSignErrorEnums.USER_NAME_IS_NOT_FOUND);
    }
    this.username = username.get(0).text();
    // 获取关注列表
    List<String> pages = getPages();
    if (!CollectionUtils.isEmpty(pages)) {
      for (String page : pages) {
        Document nextPageDocument =
            YzjHttp.createGet(page)
                .cookies(cookie)
                .config(YzjSendConfig.start().document().end())
                .defaultUserAgent()
                .send()
                .getDocument();
        List<String> pageFollows = getPageFollows(nextPageDocument);
        if (pageFollows != null) {
          this.follows.addAll(pageFollows);
        }
      }
    }
  }

  public static TiebaUtils newInstance(String cookie) {
    return new TiebaUtils(cookie);
  }

  public static String generateSignApi(String cookie) {
    JSONObject json =
        YzjHttp.createGet(TBS_URL)
            .cookies(cookie)
            .config(YzjSendConfig.start().json().end())
            .send()
            .getJson();
    Random random = new Random();
    int fid = random.nextInt(9000) + 1000;
    return SIGN_URL.replace("${TBS}", json.getString("tbs")).replace("${FID}", fid + "");
  }

  public static List<FollowStatus> sign(List<String> follows, String cookie) {
    if (!CollectionUtils.isEmpty(follows)) {
      List<FollowStatus> signs = new ArrayList<>();
      List<JSONObject> jsons = new ArrayList<>();
      Map<String, Object> map = new ConcurrentHashMap<>(2);
      String signApi = generateSignApi(cookie);
      map.put("ie", "utf-8");
      for (String follow : follows) {
        // 发送签到请求
        String replace = signApi.replace("${KW}", follow);
        log.info("开始签到 {} 吧", follow);
        map.put("kw", follow);
        YzjHttpResult send =
            YzjHttp.create()
                .url(replace)
                .method(HttpMethod.GET)
                .cookies(cookie)
                .config(YzjSendConfig.start().json().end())
                .send();
        JSONObject json = send.getJson();
        if (json != null && json.get(RESULT_NO_KEY) != null) {
          json.put("follow", follow);
        }
        log.info("请求结果 -->{}", json);
        jsons.add(json);
      }
      for (JSONObject jsonFuture : jsons) {
        try {
          JSONObject json = jsonFuture;
          if (json != null && json.get(RESULT_NO_KEY) != null) {
            String no = json.getString(RESULT_NO_KEY);
            // 如果不是已经签到过，或者签到成功，则加入签到失败列表。
            if (!no.equals(RE_SIGN) && !no.equals(SUCCESS_CODE)) {
              log.info("重试签到的吧 --> {}", json.getString("follow"));
              signs.add(new FollowStatus(json.getString("follow"), no, json.getString(MSG_KEY)));
            }
          }
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
      log.info("重试签到列表 --> {}", signs);
      return signs;
    }
    throw OpenApiException.tiebaSignException(TiebaSignErrorEnums.EMPTY_SIGN_LIST);
  }

  /**
   * 获取用户名
   *
   * @return 用户名
   */
  public String getUsername() {
    return this.username;
  }

  public List<String> getFollows() {
    return this.follows;
  }

  public List<String> getPages() {
    Document pageDocument =
        YzjHttp.createGet(FOLLOWS_URL)
            .cookies(cookie)
            .config(YzjSendConfig.start().document().end())
            .send()
            .getDocument();
    // 默认初次访问的数据，即第一页的数据。
    this.follows.addAll(getPageFollows(pageDocument));

    Elements pageElements = pageDocument.getElementsByClass(PAGINATION_DOC_CLASS);
    List<String> pages = new ArrayList<>();
    for (Element element : pageElements) {
      Elements children = element.children();
      for (Element child : children) {
        String text = child.text();
        if (StringUtils.isNumeric(text) && !text.equals(FIRST_PAGE)) {
          pages.add(getFollowPage(child.text()));
        }
      }
    }
    return pages;
  }

  private List<String> getPageFollows(Document doc) {
    List<String> follows = new ArrayList<>();
    Elements elementsByClass = doc.getElementsByClass(FORUM_TABLE_DOC_CLASS);
    if (!elementsByClass.isEmpty()) {
      Elements forumTable =
          doc.getElementsByClass(FORUM_TABLE_DOC_CLASS).get(0).getElementsByTag("a");
      if (CollectionUtils.isEmpty(forumTable)) {
        throw OpenApiException.tiebaSignException(TiebaSignErrorEnums.FORUM_TABLE_IS_EMPTY);
      }
      for (Element element : forumTable) {
        if (element.hasAttr(TD_TITLE_ATTR_NAME) && element.children().size() < 2) {
          follows.add(element.text());
        }
      }
      return follows;
    }
    return null;
  }

  private String getFollowPage(String page) {
    return FOLLOWS_URL + "?&pn=" + page;
  }
}
